<!DOCTYPE html>
<html lang="zh-CN">
<head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title>
        
            【翻译】Go 编译器理解: 新增一个关键字语句-Part2 
        
    </title>
    <link rel="icon" href="/img/favicon.png"/>
    
<link rel="stylesheet" href="/css/style.css">

    
<link rel="stylesheet" href="/css/font-awesome.min.css">

    <!-- 
<link rel="stylesheet" href="/css/gitment.css">
 -->
    
<link rel="stylesheet" href="/css/hljs.min.css">

    
<script src="/js/hljs.min.js"></script>
  
    <script>
        var _hmt = _hmt || [];
        (function() {
          var hm = document.createElement("script");
          hm.src = "https://hm.baidu.com/hm.js?8ad9e7d37411eee585b4bd7194806394";
          var s = document.getElementsByTagName("script")[0]; 
          s.parentNode.insertBefore(hm, s);
        })();
    </script>  
    <!-- 
<script src="/js/gitment.browser.js"></script>
   -->
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<meta name="generator" content="Hexo 5.3.0"><link rel="alternate" href="/atom.xml" title="Nick" type="application/atom+xml">
</head>
<body>
    <header class="header" id="header">
    <h1>
        <a class="title" href="/">
            Nick
        </a>
    </h1>
    <h2>
        <a class="motto">
            天地间的怪事莫过于侦察别人的一些和自己绝不相干的事了
        </a>
    </h2>
    <nav class="navbar">
        <ul class="menu">
            
            
                <li class="menu-item">
                    <a href="/" class="menu-item-link">
                        首页
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a href="/archives" class="menu-item-link">
                        归档
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a target="_blank" rel="noopener" href="https://github.com/StoneQI" class="menu-item-link">
                        Github
                    </a>
                </li>
            
                
            
                <li class="menu-item">
                    <a href="/atom.xml" class="menu-item-link">
                        RSS
                    </a>
                </li>
            
                
            
                
            
                <li class="menu-item">
                    <a href="/category-archive" class="menu-item-link">
                        目录
                    </a>
                </li>
            
                
                
                <li class="menu-item">
                    <a class="menu-item-link search">
                        Search                   
                        <i class="fa fa-long-arrow-right search-icon" aria-hidden="true"></i>
                    </a>
                        <input placeholder="Search..." class="search-input" style="display:none;border:none!important;" onkeydown="onEnter(event)" onkeypress="onEnter(event)"></input>
                </li>
                
        </ul>
    </nav>
</header>
    <main class="main">
        <article class="post">
            <h1>
                <a class="title" href="/2021/03/06/tr-golang-complier-part-2/"> 
                    【翻译】Go 编译器理解: 新增一个关键字语句-Part2  
                </a>
            </h1>
            <div class="meta">
                <a class="date"> 
                    <i class="fa fa-calendar" aria-hidden="true"></i>                    
                    2021-03-06   
                </a>
                
                <a class="category">
                    <i class="fa fa-th" aria-hidden="true"></i>  
                </a>
               
                <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/go/">go</a></li></ul>
                
                <a class="tag">
                    <i class="fa fa-tags" aria-hidden="true"></i>  
                </a>
                
                    <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/tags/go/" rel="tag">go</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%BC%96%E8%AF%91%E5%99%A8/" rel="tag">编译器</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/%E7%BF%BB%E8%AF%91/" rel="tag">翻译</a></li></ul>
            </div>
<div class="toc">
  <ol class="toc-list"><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#SSA"><span class="toc-list-text">SSA</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#Go-%E7%BC%96%E8%AF%91%E5%99%A8%E4%B8%AD%E7%9A%84SSA"><span class="toc-list-text">Go 编译器中的SSA</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E8%BD%AC%E6%8D%A2-until-AST-%E8%8A%82%E7%82%B9%E5%88%B0-SSA"><span class="toc-list-text">转换 until AST 节点到 SSA</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#SSA-transformations-%E5%8F%98%E6%8D%A2"><span class="toc-list-text">SSA transformations 变换</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E7%94%9F%E6%88%90%E6%9C%BA%E5%99%A8%E7%A0%81"><span class="toc-list-text">生成机器码</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E7%BB%93%E8%AE%BA"><span class="toc-list-text">结论</span></a></li><li class="toc-list-item toc-list-level-2"><a class="toc-list-link" href="#%E9%99%84%E5%BD%95-%E5%A4%8D%E7%8E%B0%E8%BF%99%E4%BA%9B%E7%BB%93%E6%9E%9C"><span class="toc-list-text">附录-复现这些结果</span></a></li></ol>
</div>
            <div class="content">
                <p>这是介绍Go编译器系列的第二篇，在[第1部分]（<a target="_blank" rel="noopener" href="https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-2/%EF%BC%89%E4%B8%AD%EF%BC%8C%E6%88%91%E4%BB%AC%E5%9C%A8Go%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%BA%86%E4%B8%80%E4%B8%AA%E6%96%B0%E7%9A%84%E5%85%B3%E9%94%AE%E8%AF%8D%E8%AF%AD%E5%8F%A5%EF%BC%8C%E9%80%9A%E8%BF%87%E6%9E%84%E5%BB%BA%E4%BF%AE%E6%94%B9%E7%89%88%E6%9C%AC%E7%9A%84%E7%BC%96%E8%AF%91%E5%99%A8%E6%9D%A5%E6%94%AF%E6%8C%81%E8%AF%A5%E5%85%B3%E9%94%AE%E5%AD%97%E3%80%82%E4%B8%BA%E6%AD%A4%EF%BC%8C%E6%88%91%E4%BB%AC%E6%8C%89%E7%85%A7%E6%AD%A4%E5%9B%BE%E4%BB%8B%E7%BB%8D%E4%BA%86%E7%BC%96%E8%AF%91%E5%99%A8%E7%9A%84%E5%89%8D%E4%BA%94%E4%B8%AA%E9%98%B6%E6%AE%B5%EF%BC%9A">https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-2/）中，我们在Go中添加了一个新的关键词语句，通过构建修改版本的编译器来支持该关键字。为此，我们按照此图介绍了编译器的前五个阶段：</a></p>
<p><img src="https://gitee.com/stoneqi/blogimage/raw/master/img/go-compiler-flow.png"></p>
<p>在最后，我们是通过在＂重写AST＂阶段将其替换为<code>for</code>来实现<code>until</code>。另外，在<code>gc/walk.go</code>中，其他类似的转换也是在编译器进行SSA转换和代码生成之前完成的。</p>
<p>在这一部分中，我们将通过在编译流程中处理新的<code>until</code>语句来介绍编译器的其余阶段。</p>
<a id="more"></a>
<h2 id="SSA"><a href="#SSA" class="headerlink" title="SSA"></a>SSA</h2><p>在<code>gc</code>运行<code>walk</code>转换后，它调用<code>buildssa</code>（在<code>gc/ssa.go</code>中）将AST转换为新的中间表示（intermediate representation (<strong>IR</strong>)），其形式为<a target="_blank" rel="noopener" href="https://en.wikipedia.org/wiki/Static_single_assignment_form">静态单赋值（SSA）形式</a>。</p>
<p>注：一个Go程序元素只有包、类型、函数、变量和常量 （packages, types, functions, variables and constants）这五种。ssa定义了的他们的数据结构，使用函数体的静态单赋值（ssa）形式中间表示（IR）。</p>
<p>SSA是什么意思？为什么编译器会将AST转为SSA？让我们从第一个问题开始；我建议阅读上面链接的SSA维基百科页面和其他资源，但这是一个快速说明。</p>
<p>静态单赋值意味着IR中分配的每个变量仅赋值一次。考虑以下伪IR：</p>
<pre><code>x = 1
y = 7
// do stuff with x and y
x = y
y = func()
// do more stuff with x and y
</code></pre>
<p>这不是SSA，因为名称x和y被多次赋值。如果将此代码段转换为SSA，我们可能会得到类似以下内容的信息：</p>
<pre><code>x = 1
y = 7
// do stuff with x and y
x_1 = y
y_1 = func()
// do more stuff with x_1 and y_1
</code></pre>
<p>请注意，每个赋值如何获得唯一的变量名。当<code>x</code>被重新分配了一个不同的值时，一个新的名字<code>x_1</code>被创建了。您可能想知道这在一般情况下是如何工作的……类似的代码会发生什么：</p>
<pre><code>x = 1
if condition: x = 2
use(x)
</code></pre>
<p>如果我们只是简单地将第二个赋值重命名为<code>x_1 = 2</code>，那么<code>use</code>将会使用 <code>x</code>或<code>x_1</code>或…？为了处理这种重要情况，SSA形式的IR具有特殊的 phi （最初为 phony ）功能，可以根据其来自的代码路径来选择一个值。它看起来像这样：</p>
<p><img src="https://gitee.com/stoneqi/blogimage/raw/master/img/simple-ssa-phi.png" alt="CFG diagram for previous example showing phi node"></p>
<p>当分析和优化此类IR时，编译器使用该phi节点来维护SSA，并在以后的阶段用实际的机器代码代替。</p>
<p>SSA名称的static 部分起着与 static类型中类似的作用；这意味着在查看源代码时（在编译时或静态），每个名称的赋值都是唯一的，但是在运行时可能会多次发生。例如上面显示的代码示例处于循环中，则实际的<code>x_1 = 2</code>赋值可能会发生多次。</p>
<p>既然我们对什么是SSA有一个基本的了解，那么下一个问题就是为什么编译器会将AST转为SSA。</p>
<p>优化是编译器后端的最重要部分，因此后端通常是专门为促进有效和高效的优化而构建的。再次查看此代码段：</p>
<pre><code>x = 1
if condition: x = 2
use(x)
</code></pre>
<p>并假设编译器要运行一个非常通用的优化-<a target="_blank" rel="noopener" href="https://zh.wikipedia.org/wiki/%E5%B8%B8%E6%95%B8%E6%8A%98%E7%96%8A">常数传播</a> （ <em>constant propagation</em>），该优化希望在<code>x = 1</code>赋值后用1替换所有使用<code>x</code>的地方。这需要怎么做呢？它不能在赋值之后找到对<code>x</code>的所有引用进行替换，因为<code>x</code>可以重写为其他内容（<code>x = 2</code>）。</p>
<p>请考虑以下代码段：</p>
<pre><code class="go">z = x + y
</code></pre>
<p>在一般情况下，编译器必须执行<em>数据流分析</em>才能找到：</p>
<ol>
<li><p><code>x</code>和<code>y</code>在什么地方定义的。在存在控制流的情况下，这并不容易，需要进行“优势”分析。 </p>
</li>
<li><p>在定义之后使用<code>z</code>的地方，这同样具有挑战性。</p>
</li>
</ol>
<p>就时间和空间而言，这种分析的创建和维护成本很高。此外，必须在每次优化后重新运行（至少部分运行）。</p>
<p>SSA提供了一个很好的选择。如果SSA中有<code>z = x + y</code>，我们会立即知道<code>x</code>和<code>y</code>定义的地方（且只能是一个），并且我们会立即知道<code>z</code>的使用位置（此语句后对<code>z</code>的所有引用）。在SSA中，使用和定义在IR中进行了编码，并且优化不会违反不变性。</p>
<h2 id="Go-编译器中的SSA"><a href="#Go-编译器中的SSA" class="headerlink" title="Go 编译器中的SSA"></a>Go 编译器中的SSA</h2><p>我们继续介绍如何在Go编译器中SSA是如何构造和使用的。 SSA是<a target="_blank" rel="noopener" href="https://blog.golang.org/go1.7">Go的相对较新的新增功能</a>。除了将AST转换为SSA的大量代码（位于<code>gc/ssa.go</code>）外，其大部分代码都保存在<code>ssa/</code>中。 <code>ssa</code>目录中的<code>README</code>文件是Go SSA的非常有用的说明（务必查看）！</p>
<p>Go SSA实现还具有一些我见过的最好的编译器工具。通过设置环境变量<code>GOSSAFUNC</code>，我们可以获得一个HTML页面，其中包含所有编译阶段以及每个编译阶段之后的IR，因此，如果需要，我们可以轻松地检测出哪个优化出错了。附加设置可以绘制每次<code>pass</code>时的控制流图。</p>
<p>让我们研究一下如何从下述代码的AST中创建初始SSA：</p>
<pre><code class="go">func usefor() &#123;
  i := 4
  for !(i == 0) &#123;
    i--
    sayhi()
  &#125;
&#125;

func sayhi() &#123;
  fmt.Println(&quot;Hello, for!&quot;)
&#125;
</code></pre>
<p>我将<code>fmt.Println(&quot;Hello, for!&quot;)</code>打印输出移至其<code>sayhi()</code>的原因是为了使结果SSA更加整洁。使用<code>-l</code>进行编译以禁用内联，这会留下一个对<code>sayhi()</code>的微小调用（而由于常量字符串，对<code>fmt.Println</code>的调用会生成更多代码）。</p>
<p>产生的SSA为：</p>
<pre><code class="GO">b1:

        v1 (?) = InitMem &lt;mem&gt;
        v2 (?) = SP &lt;uintptr&gt;
        v3 (?) = SB &lt;uintptr&gt;
        v4 (?) = Const64 &lt;int&gt; [4] (i[int])
        v6 (?) = Const64 &lt;int&gt; [0]
        v9 (?) = Const64 &lt;int&gt; [1]
    Plain → b2 (10)

    b2: ← b1 b4

        v5 (10) = Phi &lt;int&gt; v4 v10 (i[int]) // 第一次循环，选择V4
        v14 (14) = Phi &lt;mem&gt; v1 v12
        v7 (10) = Eq64 &lt;bool&gt; v5 v6
    If v7 → b5 b3 (unlikely) (10)

    b3: ← b2

        v8 (11) = Copy &lt;int&gt; v5 (i[int])
        v10 (11) = Sub64 &lt;int&gt; v8 v9 (i[int])
        v11 (12) = Copy &lt;mem&gt; v14
        v12 (12) = StaticCall &lt;mem&gt; &#123;&quot;&quot;.sayhi&#125; v11
    Plain → b4 (12)

    b4: ← b3
    Plain → b2 (10)

    b5: ← b2

        v13 (14) = Copy &lt;mem&gt; v14
    Ret v13
</code></pre>
<p>这里要注意的有趣部分是：</p>
<ul>
<li><code>bN</code> 是控制流程图的基本块（一个作用域）.</li>
<li><code>Phi</code> 节点是显式的。 最有趣的是对<code>v5</code>的赋值。这正是选择器给<code>i</code>赋值的语句：一条路径来自<code>v4</code>（初始化赋值），另一条路径来自<code> v10</code>（<code> i--</code>）循环。</li>
<li>在本练习中，忽略带有<code>&lt;mem&gt;</code>的节点。 Go有一种有趣的方式可以在其IR中显式传播内存状态，在本文中我们将不涉及它。如果有兴趣的话，请参阅前面的<code>README</code>了解更多详细信息。</li>
</ul>
<p>顺便说一句，这里的<code>for</code>循环正是我们想要将<code>until</code>语句转换成的。</p>
<h2 id="转换-until-AST-节点到-SSA"><a href="#转换-until-AST-节点到-SSA" class="headerlink" title="转换 until AST 节点到 SSA"></a>转换 <code>until</code> AST 节点到 SSA</h2><p>像往常一样，我们的代码将以<code>for</code>语句的处理为模型。首先，让我们开始看下<code>until</code>语句的控制流图 ：</p>
<p><img src="https://gitee.com/stoneqi/blogimage/raw/master/img/until-cfg.png" alt="CFG for until nodes"></p>
<p>现在，我们只需要用代码构建此控制流图 。提醒：我们在<a target="_blank" rel="noopener" href="https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-1/">第1部分</a>中添加了新的AST节点类型<code>OUNTIL</code>。我们将在<code>gc/ssa.go</code>中的<code>state.stmt</code>方法中添加一个新的<code>switch</code>分支，以将<code>OUNTIL</code>的AST节点转换为SSA。代码块和注释的命名应使代码易于阅读，并与上面显示的控制流图中命名相关。</p>
<pre><code class="go">case OUNTIL:
  // OUNTIL: until Ninit; Left &#123; Nbody &#125;
  // cond (Left); body (Nbody)
  bCond := s.f.NewBlock(ssa.BlockPlain)
  bBody := s.f.NewBlock(ssa.BlockPlain)
  bEnd := s.f.NewBlock(ssa.BlockPlain)

  bBody.Pos = n.Pos

  // 首先 ，上一个 Block结束块 连接到当前的 CondBlock块
  // first, entry jump to the condition
  b := s.endBlock()
  b.AddEdgeTo(bCond)
  // 开始生成 CondBlock
  // generate code to test condition
  s.startBlock(bCond)
  if n.Left != nil &#123;
    // left 不为空，直接使用condBranch，if for 都使用这个函数
    s.condBranch(n.Left, bEnd, bBody, 1)
  &#125; else &#123;
    // left 为空 无限循环。直接结束生成代码
    b := s.endBlock() 
    b.Kind = ssa.BlockPlain
    b.AddEdgeTo(bBody) // 添加到 Body的连接
  &#125;

  // 设置 continue/break 
  // set up for continue/break in body
  prevContinue := s.continueTo
  prevBreak := s.breakTo
  s.continueTo = bCond
  s.breakTo = bEnd
  lab := s.labeledNodes[n]
  if lab != nil &#123;
    // labeled until loop
    lab.continueTarget = bCond
    lab.breakTarget = bEnd
  &#125;

  // 开始 生成 Body代码
  // generate body
  s.startBlock(bBody)
  s.stmtList(n.Nbody)
  
  // 拆除 continue/break，这里是置为上一个 prevContinue和 prevBreak
  // 因为有循环嵌套
  // tear down continue/break
  s.continueTo = prevContinue
  s.breakTo = prevBreak
  if lab != nil &#123;
    lab.continueTarget = nil
    lab.breakTarget = nil
  &#125;

  // done with body, goto cond
  if b := s.endBlock(); b != nil &#123;
    b.AddEdgeTo(bCond)
  &#125;
  // 生成结束代码
  s.startBlock(bEnd)
</code></pre>
<p>如果您想知道<code>n.Ninit</code>在哪里处理-它在<code>switch</code>之前完成，对于所有节点类型都是统一的。</p>
<p>实际上，这是我们在编译器的最后阶段处理 <code>until</code>语句的全部工作！如果我们运行编译器，像以前一样打印出下述代码的SSA：</p>
<pre><code class="go">func useuntil() &#123;
  i := 4
  until i == 0 &#123;
    i--
    sayhi()
  &#125;
&#125;

func sayhi() &#123;
  fmt.Println(&quot;Hello, for!&quot;)
&#125;
</code></pre>
<p>如预期的那样，我们将得到SSA，该SSA在结构上等同于条件为 <code>for</code>的循环所用的SSA。</p>
<h2 id="SSA-transformations-变换"><a href="#SSA-transformations-变换" class="headerlink" title="SSA transformations 变换"></a>SSA transformations 变换</h2><p>构造了初始SSA之后，编译器会在SSA IR上执行以下一系列漫长的遍历操作：</p>
<ol>
<li>执行优化</li>
<li> 将其转换为更接近机器代码的形式 </li>
</ol>
<p>所有 passes 都可以在<code>ssa/compile.go</code>的<code>passes</code>切片中找到，并且可以在同一文件的<code>passOrder</code>切片中对它们的运行顺序进行一些修改。对于现代编译器而言，优化是相当标准的。降低针对特定体系结构的指令选择，以及寄存器分配。</p>
<p>有关这些 passes 的更多详细信息，请参见<a target="_blank" rel="noopener" href="https://github.com/golang/go/blob/master/src/cmd/compile/internal/ssa/README.md">SSA README</a>和<a target="_blank" rel="noopener" href="https://quasilyte.dev/blog/post/go_ssa_rules/">this post</a>，其中详细介绍了如何指定SSA优化规则。</p>
<h2 id="生成机器码"><a href="#生成机器码" class="headerlink" title="生成机器码"></a>生成机器码</h2><p>最后，编译器调用<code>genssa</code>（在<code>gc/ssa.go</code>中）从SSA IR中生成机器代码。我们不必修改任何代码，因为我们为<code>until</code>语句生成的SSA构造块，是编译器已经实现的，我们不添加新的指令类型。</p>
<p>但是，研究<code>useuntil</code>函数生成的机器代码是有学习知道意义的。 Go具有<a target="_blank" rel="noopener" href="https://golang.org/doc/asm">自己的汇编语法</a>。我不会在这里讨论所有细节，但是下面是带注释的（带有<code>＃注释</code>）汇编实现，应该很容易阅读。我删除了一些垃圾回收器（garbage collector）的指令（<code>PCDATA</code>和<code> FUNCDATA</code>），以使输出变小。</p>
<pre><code class="assembly">&quot;&quot;.useuntil STEXT size=76 args=0x0 locals=0x10
  0x0000 00000 (useuntil.go:5)  TEXT  &quot;&quot;.useuntil(SB), ABIInternal, $16-0

  # Function prologue

  0x0000 00000 (useuntil.go:5)  MOVQ  (TLS), CX
  0x0009 00009 (useuntil.go:5)  CMPQ  SP, 16(CX)
  0x000d 00013 (useuntil.go:5)  JLS  69
  0x000f 00015 (useuntil.go:5)  SUBQ  $16, SP
  0x0013 00019 (useuntil.go:5)  MOVQ  BP, 8(SP)
  0x0018 00024 (useuntil.go:5)  LEAQ  8(SP), BP

  # AX will be used to hold &#39;i&#39;, the loop counter; it&#39;s initialized
  # with the constant 4. Then, unconditional jump to the &#39;cond&#39; block.

  0x001d 00029 (useuntil.go:5)  MOVL  $4, AX
  0x0022 00034 (useuntil.go:7)  JMP  62

  # The end block is here, it executes the function epilogue and returns.

  0x0024 00036 (&lt;unknown line number&gt;)  MOVQ  8(SP), BP
  0x0029 00041 (&lt;unknown line number&gt;)  ADDQ  $16, SP
  0x002d 00045 (&lt;unknown line number&gt;)  RET

  # This is the loop body. AX is saved on the stack, so as to
  # avoid being clobbered by &quot;sayhi&quot; (this is the caller-saved
  # calling convention). Then &quot;sayhi&quot; is called.

  0x002e 00046 (useuntil.go:7)  MOVQ  AX, &quot;&quot;.i(SP)
  0x0032 00050 (useuntil.go:9)  CALL  &quot;&quot;.sayhi(SB)

  # Restore AX (i) from the stack and decrement it.

  0x0037 00055 (useuntil.go:8)  MOVQ  &quot;&quot;.i(SP), AX
  0x003b 00059 (useuntil.go:8)  DECQ  AX

  # The cond block is here. AX == 0 is tested, and if it&#39;s true, jump to
  # the end block. Otherwise, it jumps to the loop body.

  0x003e 00062 (useuntil.go:7)  TESTQ  AX, AX
  0x0041 00065 (useuntil.go:7)  JEQ  36
  0x0043 00067 (useuntil.go:7)  JMP  46
  0x0045 00069 (useuntil.go:7)  NOP
  0x0045 00069 (useuntil.go:5)  CALL  runtime.morestack_noctxt(SB)
  0x004a 00074 (useuntil.go:5)  JMP  0
</code></pre>
<p>您可能已经注意到<code>cond</code>块移到了函数的末尾，而不是它最初在SSA表示中的位置。这是为什么?</p>
<p>答案是最后阶段在SSA上运行的<code>loop rotate</code> pass。此 pass 会对块进行重新排序，以使 body flows直接流入cond，从而避免每次迭代产生额外的跳跃。如果您有兴趣，请参见<code>ssa/looprotate.go</code>了解更多详细信息。</p>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>在这两篇文章中，我们以两种不同的方式实现了一条新语句，从而了解了Go编译器的一些内部结构。当然，这只是冰山一角，但我希望它为您自己开始探索提供了一个良好的起点。</p>
<p>最后一点：我们在这里构建了一个可运行的编译器，但是Go工具都无法识别新的<code>until</code>关键字。不幸的是，此时Go工具使用了完全不同的方法来解析Go代码，并且没有与Go编译器本身共享此代码（例如gopls ，golint，所以不能针对新关键字自动实现自动补全之类的功能）。我将在以后的文章中详细介绍如何使用工具处理Go代码。</p>
<h2 id="附录-复现这些结果"><a href="#附录-复现这些结果" class="headerlink" title="附录-复现这些结果"></a>附录-复现这些结果</h2><p>要重现我们最终在此处使用的Go工具链的版本，您可以从<a target="_blank" rel="noopener" href="https://eli.thegreenplace.net/2019/go-compiler-internals-adding-a-new-statement-to-go-part-1/">第1部分</a>开始，在<code> walk.go</code>中还原 AST 转换代码，然后添加上述 AST-&gt; SSA转换。或者，您也可以从我的仓库上抓取<a target="_blank" rel="noopener" href="https://github.com/eliben/go/tree/adduntil2">adduntil2分支</a>。</p>
<p>要获得从所有SSA的<code>until</code>SSA，并在单个方便的HTML文件中传递代码生成，请在构建新编译器后运行以下命令：</p>
<pre><code class="go">GOSSAFUNC=useuntil &lt;src checkout&gt;/bin/go tool compile -l useuntil.go
</code></pre>
<p>然后在浏览器中打开<code>ssa.html</code>。如果您还想查看控制流图的某些 pass，请在函数名后添加 pass 名，并以<code>:</code>分隔。 例如 <code>GOSSAFUNC=useuntil:number_lines</code>.</p>
<p>要获取机器代码，请运行：</p>
<pre><code class="go">&lt;src checkout&gt;/bin/go tool compile -l -S useuntil.go
</code></pre>

            </div>
          
           
            <div class="copyright">
                <div class="name">
                    <a>本文作者:</a>
                    <a>Nick Yang</a>
                </div>
                <div class="link">
                    <a>本文链接:</a>
                    <a class="permalink" href="https://stoneqi.github.io/2021/03/06/tr-golang-complier-part-2/">https://stoneqi.github.io/2021/03/06/tr-golang-complier-part-2/</a>
                </div>
                <div class="license">
                    <a>版权声明:</a>
                    <a>本博客所有文章除特别声明外，均采用许可协议 CC BY-NC-SA 3.0 CN。转载请注明出处！</a>
                </div>
            </div>
            

          


</article>


<div class="tip">
<button class="tip-btn">
    打赏
</button>
<div class="tip-img">
<ul>
    
<li>
    <img src="/img/aipay.jpg"></img>
</li>

 
<li>
    <img src="/img/wechatpay.png"></img>
</li>

</ul>
</div>
</div>

<div class="more">

<div></div>

    <div class="next">
    <a href="/2021/03/05/tr-golang-complier-part-1-md/"> 【翻译】Go 编译器理解: 新增一个关键字语句-Part 1 </a>
    </div>
    
</div>

<div class="bdsharebuttonbox">
<a href="#" class="bds_weixin fa fa-weixin" data-cmd="weixin" title="分享到微信" style="color:#1cbd8f">
</a>
<a href="#" class="bds_tsina fa fa-weibo" data-cmd="tsina" title="分享到新浪微博" style="color:#ff6363">
</a>
<a href="#" class="bds_twi fa fa-twitter" data-cmd="twi" title="分享到Twitter" style="color:#00A7EB">
</a>
<a href="#" class="bds_fbook fa fa-facebook" data-cmd="fbook" title="分享到Facebook" style="color:#00A7EB">
</a>
</div>
<script>
window._bd_share_config={
    "common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"1","bdSize":"16"},
    "share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='../../../../../static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];
</script>
 
<!-- <div id="comments"></div> -->
<div id="gitalk-container"></div>

<script>
  //   var gitment = new Gitment({
  //   id: "Sat Mar 06 2021 15:41:49 GMT+0800",
  //   owner: "StoneQi",
  //   repo: "stoneqi.github.io",
  //   oauth: {
  //     client_id:"d9f4e8907ec2566c2307",
  //     client_secret: "2390fe6084c9227566e82de1755e3c3de4a13592",
  //   }
  // })
  // gitment.render('comments')

  const gitalk = new Gitalk({
  clientID: "d9f4e8907ec2566c2307",
  clientSecret: "2390fe6084c9227566e82de1755e3c3de4a13592",
  repo: "stoneqi.github.io",      // The repository of store comments,
  owner: "StoneQi",
  admin: ["StoneQi"],
  language: "zh-CN",
  id: "Sat Mar 06 2021 15:41:49 GMT+0800",      // Ensure uniqueness and length less than 50
  distractionFreeMode: false  // Facebook-like distraction free mode
})

gitalk.render('gitalk-container')
</script>
    </main>
    <a class="not-found">not found!</a>
    <div class="search-items">
    </div>
    <a href="#header" id="top" style="display:none">
        <i class="fa fa-sort-asc fa-2x"></i>
    </a>
    <footer class="footer">
    <div class="footer-copyright">©️2017
    <a href="//github.com/Vevlins/toki" class="link" target="_blank">Toki</a>  by Vevlins
    </div>    
</footer>

    
<script src="/js/jquery.js"></script>

    
<script src="/js/toki.js"></script>
  
  <script>hljs.initHighlightingOnLoad();</script>
</body>
</html>